package edu.umn.cs5115.scheduler;

import java.text.SimpleDateFormat;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.ExpandBar;
import org.eclipse.swt.widgets.ExpandItem;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import edu.umn.cs5115.scheduler.entities.Course;
import edu.umn.cs5115.scheduler.entities.CoursePart;
import edu.umn.cs5115.scheduler.entities.MeetingTime;
import edu.umn.cs5115.scheduler.entities.Schedule;
import edu.umn.cs5115.scheduler.entities.Section;
import edu.umn.cs5115.scheduler.entities.Weekday;
import edu.umn.cs5115.scheduler.framework.KeyValueCoding;
import edu.umn.cs5115.scheduler.framework.KeyValueObserver;
import edu.umn.cs5115.scheduler.framework.KeyValueObserver.SetMutationType;
import edu.umn.cs5115.scheduler.framework.gui.SelectionController;
import edu.umn.cs5115.scheduler.search.SearchResults;
import edu.umn.cs5115.scheduler.util.WidgetUtil;

public class ConsideringList extends Composite implements KeyValueObserver,
		Listener {

	private SchedulerDocument document;

	private Group courseListGroup;
	private ExpandBar courseListContainer;
	
	private Hashtable<Course, Tree>       courseTreeReference;
	private Hashtable<Section, TreeItem>  sectionTItemReference;
	private Hashtable<Course, ExpandItem> courseContainerReference;
	
	public ConsideringList(Composite parent, int style, SchedulerDocument document) {
		super(parent, style);
		
		this.document = document;
		
		this.courseContainerReference = new Hashtable<Course, ExpandItem>();
		this.courseTreeReference = new Hashtable<Course, Tree>();
		this.sectionTItemReference = new Hashtable<Section, TreeItem>();
		
		this.setLayout(new GridLayout());
		
		initializeWidgets();
	}
	
	public void initializeWidgets() {
		
		// create course group
		this.courseListGroup = new Group(this, SWT.BORDER);
		GridLayout layout = new GridLayout();
		layout.marginHeight = layout.marginWidth = 0;
		this.courseListGroup.setLayout( layout );
		this.courseListGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
		this.courseListGroup.setText("Courses I am considering:");
		
		// create expand-bar list of courses
		courseListContainer = new ExpandBar (this.courseListGroup, SWT.V_SCROLL );
		courseListContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
		courseListContainer.setSpacing(5);
		
		// initialize LISTENERS
		document.getSelectionController().addListener(this, SelectionController.SELECTION_KEY);
		document.getSchedule().addListener(this, Schedule.COURSES_KEY);
		
		// we now want to render any existing courses from the CourseList to the 'Courses I am Considering' tree
		addCoursesToList(this.document.getSchedule().getCourses());
	}
	
	public void valueChanged(KeyValueCoding object, String key) {
		System.out.println("CourseListListener.valueChanged(..., ...)");
		System.out.println("coding = '" + object.toString() + "'");
		System.out.println("key = '" + key.toString() + "'");

		System.out.println("Coding is an innstance of SelectionController");
		
		if ( object instanceof SelectionController ) {
			// first, let's de-select all the trees
			deselectAll();
			
			SelectionController<CoursePart> selectionController = (SelectionController<CoursePart>)object;
			CoursePart part = ((SelectionController<CoursePart>)object).getSelection();
			if ( part != null ) {
				// some calendar object was selected.  select corresponding tree item
				System.out.println("part = '" + part.toString() + "'");
				
				// find the tree item
				if ( part instanceof Section ) {
					TreeItem selectMe = this.sectionTItemReference.get(part);
					expandParents(selectMe);
	//				selectMe.getParent().setSelection(selectMe);
					
				}
			}
		} else if ( object instanceof Section ) {
			// TODO: implement
			System.out.println("Section!");
			Section section = (Section)object;
			TreeItem item = this.sectionTItemReference.get(section);
			item.setChecked(section.isChosen());
		}
	}

	/* (non-Javadoc)
	 * @see edu.umn.cs5115.scheduler.framework.KeyValueObserver#valueChanged(edu.umn.cs5115.scheduler.framework.KeyValueCoding, java.lang.String, edu.umn.cs5115.scheduler.framework.KeyValueObserver.SetMutationType, java.util.Set)
	 * 
	 * Updates list on the left for UNION and MINUS operations.
	 * 
	 */
	public void valueChanged(KeyValueCoding object, String key,
		SetMutationType mutation, Set modifyingSet) {
	
		System.out.println("CourseListListener.valueChanged(..., ..., ..., ...)");
		
		if ( object instanceof Schedule ) {
			System.out.println("schedule = '" + object.toString() + "'");
			System.out.println("key = '" + key.toString() + "'");
			System.out.println("mutation = '" + mutation.toString() + "'");
			System.out.println("modifyingSet = '" + modifyingSet + "'");			
			if ( mutation == SetMutationType.UNION ) {
				addCoursesToList(modifyingSet);
			} else if ( mutation == SetMutationType.MINUS ) {
				removeCoursesFromList(modifyingSet);
			}
		}
	}

	public void handleEvent(Event event) {
		// this event is for when a tree item is clicked
		if ( event.widget instanceof Tree ) {
			TreeItem item = (TreeItem)event.item;
			if (event.detail == SWT.CHECK) {
				// the point of listening, is to do the corresponding check on the section
				
				
				Section section = (Section)item.getData();
				// action is CHECK
				if (item.getChecked()) {
					
					section.setChosen(true);
					
					// also, expand the item
					item.setExpanded(true);
					Event expand = new Event();
					expand.widget = item.getParent();
					expand.item = item;
					expand.detail = SWT.Expand;
					item.getParent().notifyListeners(SWT.Expand, expand);
				}
				// action is UNCHECK
				else {
					section.setChosen(false);
				}
			} else {
				System.out.println("---  setting selection ---");
				// the point of listening is to set the selection in the document currentSelection
				// for the benefit of the Calendar widget
				CoursePart newSelection = (CoursePart)item.getData();
				this.document.getSelectionController().setSelection(newSelection);
			}
		}
	}

	private void expandParents(TreeItem item) {
		Tree itemTree          = item.getParent();
		Course course          = ((CoursePart)item.getData()).getCourse();
		
		// first, let's expand the course container if not yet done so
		ExpandItem courseContainer = this.courseContainerReference.get(course);
		courseContainer.setExpanded(true);
		itemTree.setSelection(item);
		/* Apparently, this is done automatically when you select an item from the tree
		 * 
		// now, expand the item's parent, if it has one
		if ( parentSection != null ) {
			Event event = new Event();
			event.widget = itemTree;
			event.item = parentSection;
			event.detail = SWT.Expand;
			itemTree.notifyListeners(SWT.Expand, event);
		}
		
		// if no parent, then this section could have child nodes.
		else if ( item.getItemCount() > 0 ) {
			Event event = new Event();
			event.widget = itemTree;
			event.item = item;
			event.detail = SWT.Expand;
			itemTree.notifyListeners(SWT.Expand, event);
		}
		
		itemTree.setSelection(item);
		*/
	}
	
	/**
	 * De-selects on all of the trees
	 */
	private void deselectAll() {
		Iterator<Tree> treeIter = this.courseTreeReference.values().iterator();
		while (treeIter.hasNext() )
			treeIter.next().deselectAll();
	}
	
	/**
	 * Removes a list of courses from the 'Courses I am Considering' list
	 * 
	 * @param courses
	 */
	public void removeCoursesFromList(Set courses) {
		Iterator<Course> courseIter = courses.iterator();
		
		// traverse the courses
		while (courseIter.hasNext()) {
			Course course = courseIter.next();
			
			// get the expand item
			ExpandItem item = this.courseContainerReference.get(course);
			Composite itemComposite = (Composite)item.getControl();
			Tree sectionTree = this.courseTreeReference.get(course);
			
			// get the tree items
			TreeItem[] sectionItems = sectionTree.getItems();
			for (int i = 0; i < sectionItems.length; i++) {
				this.sectionTItemReference.remove( ((Section)sectionItems[i].getData()) );
				sectionItems[i].dispose();
			}
			
			this.courseContainerReference.remove(item);
			this.courseTreeReference.remove(course);
			
			itemComposite.dispose();
			sectionTree.dispose();
			item.dispose();
		}
	}
	
	/**
	 * Adds items from parameter to the "Courses I am Considering" list on the left
	 * 
	 * @param courseList Set of Courses to add
	 */
	public void addCoursesToList(Set courseList) {
		// now, let's get our course list from the document
		Iterator<Course> courseIter = courseList.iterator();
		// traverse the courses
		while (courseIter.hasNext()) {
			Course course = courseIter.next();
			
			// create a composite to hold the section tree
			Composite composite = new Composite(this.courseListContainer, SWT.NONE);
			GridLayout layout = new GridLayout();
			layout.marginWidth = 10;
			layout.marginHeight = 0;
			composite.setLayout(layout);
//			composite.setBackgroundMode(SWT.INHERIT_FORCE);
//			composite.setSize(courseListContainer.getSize().x - 20, SWT.DEFAULT);
			
			// create a label with the course name
			Label courseLabel = new Label(composite, SWT.WRAP | SWT.BORDER);
			courseLabel.setText(course.getName());
			courseLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
			
			// create a section tree for this course
			Tree tree = new Tree(composite, SWT.CHECK | SWT.BORDER );
//			tree.setBackgroundMode(SWT.INHERIT_FORCE);
			GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
			tree.setLayoutData(data);
			tree.addListener(SWT.Selection, this);
			tree.getHorizontalBar().setVisible(false);
			
			// traverse 1st-level sections
			Iterator<Section> secIter = course.getSections().iterator();
			while (secIter.hasNext()) {
				Section section = secIter.next();
				TreeItem sectionItem = new TreeItem(tree, SWT.CHECK);
				sectionItem.setBackground(this.courseListContainer.getBackground());
				sectionItem.setText( CourseFormatter.sectionToString( (section) ) );
				sectionItem.setData( (CoursePart)section );
				if ( section.isChosen() )
					sectionItem.setChecked(true);
				sectionTItemReference.put(section, sectionItem);
				section.addListener(this, Section.CHOSEN_KEY);
				
				// traverse 2nd-level sections
				Iterator<Section> subSecIter = section.getSubSections().iterator();
				while (subSecIter.hasNext()) {
					Section subSection = subSecIter.next();
					TreeItem subSectionItem = new TreeItem(sectionItem, SWT.CHECK);
					subSectionItem.setBackground(this.courseListContainer.getBackground());
					subSectionItem.setText( CourseFormatter.sectionToString( subSection) );
					subSectionItem.setData( (CoursePart)subSection );
					if ( subSection.isChosen() )
						subSectionItem.setChecked(true);
					sectionTItemReference.put(subSection, subSectionItem);
					subSection.addListener(this, Section.CHOSEN_KEY);
				}
			}
			
			// create an expanditem for this course
			ExpandItem item = new ExpandItem(this.courseListContainer, SWT.NONE);
			item.setText( CourseFormatter.courseToString(course) );
			item.setHeight(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
			item.setControl( composite );
			composite.setData("parentExpandItem", item);
			
			tree.addTreeListener( new TreeListener() {
				public void treeExpanded(TreeEvent event) {
					System.out.println("treeExpanded()...");
					Tree tree = (Tree)event.widget;
					
					// expand the tree item
					TreeItem titem = (TreeItem)event.item;
					titem.setExpanded(true);
					
					// redo the sizes
					recalculateSize(tree);
				}
				public void treeCollapsed(TreeEvent event) {
					System.out.println("treeCollapsed()...");
					Tree tree = (Tree)event.widget;
					
					// collapse the tree item
					TreeItem titem = (TreeItem)event.item;
					titem.setExpanded(false);
					
					// redo the sizes
					recalculateSize(tree);
				}
				
				public void recalculateSize(Tree tree) {
					// get the parents
					Composite composite = tree.getParent();
					ExpandItem parentContainer = (ExpandItem)tree.getParent().getData("parentExpandItem");
					
					//	count the nodes in the tree to calculate height
					int nodes = WidgetUtil.countTreeHeight(tree);
					int newTreeHeight = tree.getItemHeight() * nodes;
					int oldTreeHeight = tree.getSize().y;
					int newCompositeHeight = composite.getSize().y + (newTreeHeight - oldTreeHeight);
					
					// set new heights
					tree.setSize(tree.getSize().x, newTreeHeight);
					composite.setSize(tree.getSize().x, newCompositeHeight);
					parentContainer.setHeight( newCompositeHeight + 20 );
				}
			});
			
			this.courseContainerReference.put( course, item );
			this.courseTreeReference.put(course, tree);
		}
	}
	
	
	/**
	 * @author danik
	 * I am a class that translates from Course and Section to a String label used in LeftPanel.java
	 *
	 */
	static class CourseFormatter {
		/**
		 * Returns the label to use in the left panel for any given course
		 * 
		 * @param course The course for which we want a label
		 * @return String Course label
		 */
		public static String courseToString(Course course) {
			String label =  course.getDepartment() + " " + 
							course.getCourseNumber() /*+ " - " + 
							course.getName()*/;
			
			return label;
		}
		
		/**
		 * Returns the label to use in the left panel for any given section
		 * 
		 * @param section The section for which we want a label
		 * @return String Section label
		 */
		public static String sectionToString(Section section) {
			String label = section.getType() + " " + section.getSectionNumber() + ":";
			
			// set times
			Set<MeetingTime> times = section.getMeetingTimes();
			if ( times.size() == 1 ) {
				label = label + CourseFormatter.meetingTimeToString(times.iterator().next());
			} else {
				Iterator<MeetingTime> timeIter = times.iterator();
				while (timeIter.hasNext())
					label = label + "\n        " + CourseFormatter.meetingTimeToString(timeIter.next());
			}
			
			return label;
		}
		
		/**
		 * Returns the label to use in the left panel for any given MeetingTime
		 * 
		 * @param meeting The MeetingTime for which we want a label
		 * @return String MeetingTime label
		 */
		private static String meetingTimeToString(MeetingTime meeting) {
			SimpleDateFormat timeFormatter = new SimpleDateFormat("hh:mm a");
			StringBuffer label = new StringBuffer();
			
			// start time (common but optional)
			if ( meeting.getStartTime() != null )
				label.append(" ").append(timeFormatter.format( meeting.getStartTime() ));
			// end time (common but optional)
			if ( meeting.getEndTime() != null ) {
				label.append(" - ").append(timeFormatter.format( meeting.getEndTime() ));
				label.append(",");
			}
			
			Iterator<Weekday> daysIter = meeting.getMeetingDays().iterator();
			while (daysIter.hasNext()) {
				label.append(" ").append(daysIter.next());
			}
			
			return label.toString();
		}
	}

}
